<?php
/**
 * GeryEngine
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this release in the file LICENSE
 *
 * @copyright  Copyright (c) 2007 Arjen Brouwer
 * @license    New BSD License
 * @author     Arjen Brouwer
 */

class TemplateReferenceCompilerReturn {
	public $evaluated;
	public $value;	
}

class TemplateReferenceCompiler {

	/**
	 * Sets the value of a reference
	 *
	 * @param IGeryContext $context
	 * @param unknown_type $reference
	 * @param unknown_type $value
	 * @return unknown
	 */
	public static function setValue(IGeryContext $context, $reference, $value) {
		
		// if we haven't got any modifiers, just set the variable
		if (! count($reference[3])) 
		{
			// check if a index is give
			if (count($reference[4])) 
			{
				$lastIndex = array_pop($reference[4]);
				
				$tempVar =& $context->get($reference[0]);
				
				foreach ($reference[4] as $index) 
				{
					if (! is_array($tempVar) || ! array_key_exists($index, $tempVar))
						return false;
						
					$tempVar =& $tempVar[$index];
				}
				
				// assign the value
				if (! is_array($tempVar))
					return false;
					
				$tempVar[$lastIndex] = $value;
			} 
			else 
			{
				$context->put($reference[0], $value);
			}
			
			return true;
		} 
		else 
		{
			// get the modifiers
			$modifiers = $reference[3];
			
			// pop the last modifier
			$lastModifier = array_pop($modifiers);
			
			// reassign the modifiers to the reference without the last modifier
			$reference[3] = $modifiers;
			
			// evalueate the reference
			$ret = self::evaluateReference($context, $reference);
			
			// if the reference could be evaluated
			if ($ret->evaluated) {
				
				$var = $ret->value;
				
				if (is_object($var)) {
					$methodName = 'set' . $lastModifier[0];
					
					if (method_exists($var, $methodName)) {
						$var->$methodName($value);
						return true;
					}
				} else {
					/* @todo */
					die("DONT KNOW WHAT TO DO");
				}
			}
		}
		
		// failed to set the variable
		return false;	
	}
	
	/**
	 * Evaluate a reference
	 *
	 * @param Context $context
	 * @param array $reference
	 * @return TemplateReferenceCompilerReturn
	 */
	public static function evaluateReference(IGeryContext $context, RuntimeServices $rs, $reference) {	
		
		// get reference value
		$var = $context->get($reference[0]);
		
		// no value, so return false
		if ($var === null)
			return self::returnFalse();
		
		if ($var instanceof ReferenceProxy)	{
			$var = $var->invoke();
			
			if ($var === null)
				return self::returnFalse();
		}
		
		// fetch the indexes
		foreach ($reference[4] as $index) {
			if ( is_array($var) && array_key_exists($index, $var)) {
				$var = $var[$index];
			} else if (is_string($var) && is_int($index)) {
				$var = substr($var, $index, 1);
			} else {
				return self::returnFalse();
			}
		}
		
		// if this reference does not have any modifiers, just return it
		if (! count($reference[3]))
			return self::returnTrue($var);
		
		return self::getValue($rs, $var, $reference[3]);
	}
	
	public static function getValue(RuntimeServices $rs, $var, $modifiers) {
		
		foreach ($modifiers as $modifier) 
		{
			$name = $modifier[0];
			$line = $modifier[3];
			
			// fetch parameters
			$parameters = (array_key_exists(1, $modifier))
				? $modifier[1]
				: array();
				
			// handle arrays
			if (is_array($var) || $var instanceof IGeryArray) 
			{
				// array has no parameters and the modifier exists as a key
				if (! count($parameters) && array_key_exists($name, $var)) {
					$var = $var[$name];
				
				// check if the modifier is a valid method for the array
				} 
				else 
				{
					$ret = $rs->getEngine()->getType('array')->call($name, $var, $parameters, $rs, $line);

					if ($ret === null) {
						return self::returnFalse();
					} else  {
						$var = $ret;
					}
				}
				
			// handle strings
			} 
			else if (is_string($var)) 
			{
				$ret = $rs->getEngine()->getType('string')->call($name, $var, $parameters, $rs, $line);

				if ($ret === null)
					return self::returnFalse();
				else 
					$var = $ret;
				
			// integers
			} 
			else if (is_int($var)) 
			{
				$ret = $rs->getEngine()->getType('integer')->call($name, $var, $parameters, $rs, $line);

				if ($ret === null)
					return self::returnFalse();
				else 
					$var = $ret;
			} 
			
			// floats
			else if (is_float($var)) 
			{
				$ret = $rs->getEngine()->getType('float')->call($name, $var, $parameters, $rs, $line);

				if ($ret === null)
					return self::returnFalse();
				else 
					$var = $ret;				
			}
			
			else if (is_object($var)) 
			{
				
				switch ($name) {
					case 'clone':
						$var = clone $var;
						break;
						
					default:
						$tryMethods = array(
							''
							,'get'
							,'is'
							,'has'
						);
						
						foreach ($tryMethods as $methodSuffix) {
							$methodName = $methodSuffix . ucfirst($name);
							
							if (method_exists($var, $methodName)) {
								$callback = array($var, $methodName);
								$var = call_user_func($callback, $parameters);
								break 2;
							}
						}
						
						return self::returnFalse();
				}
			}

			
			// unknown modifier
			else 
			{
				$rs->error("Unknown modifier '$name' for " . ucfirst(gettype($var)));
				return self::returnFalse();
			}
			
			// handle indexes
			foreach ($modifier[2] as $index) 
			{
				// handle array indexes
				if (is_array($var) && array_key_exists($index, $var)) 
				{
					$var = $var[$index];
				} 
				
				// handle string indexes
				else if (is_string($var) && is_int($index)) 
				{
					$var = substr($var, $index, 1);
				}
				
				// indexes not supported 
				else 
				{
					return self::returnFalse();
				}
			}
		}
		
		
		return self::returnTrue($var);
	}
	
	private static function returnFalse() {
		
		$return = new TemplateReferenceCompilerReturn();
		$return->evaluated = false;
		
		return $return;
	}
	
	private static function returnTrue($var) 
	{
		$return = new TemplateReferenceCompilerReturn();
		$return->evaluated = true;
		$return->value = $var;
		
		return $return;
	}
}
?>